"use client";
import { useEffect, useRef, useState, useContext, useCallback } from "react";
import { CodeGenerationParams, generateCode } from "./generateCode";
import { detectComponents } from "./detectComponent";
import Spinner from "./components/Spinner";
import {
  FaCode,
  FaDesktop,
  FaDownload,
  FaCopy,
  FaChevronLeft,
} from "react-icons/fa";

import { Switch } from "./components/ui/switch";
import { Button } from "./components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "./components/ui/tabs";
import { AppState, GeneratedCodeConfig } from "./types";
import html2canvas from "html2canvas";
import HistoryDisplay from "./components/history/HistoryDisplay";
import { extractHistoryTree } from "./components/history/utils";
import toast from "react-hot-toast";
import { UploadFileContext } from './contexts/UploadFileContext';
import { SettingContext } from './contexts/SettingContext';
import { HistoryContext } from './contexts/HistoryContext';
import { EditorContext, deviceType } from './contexts/EditorContext';
import UpdateChatInput from './components/chatInput/Update';
import dynamic from "next/dynamic";
import { useDebounceFn } from 'ahooks';
import { useRouter } from 'next/navigation';
import copy from "copy-to-clipboard";
import CodePreview from './components/CodePreview';
import classNames from "classnames";
import { useRouter as useNextRouter } from 'next/router';
import SettingsDialog from './components/SettingsDialog';

const CodeTab = dynamic(
  async () => (await import("./components/CodeTab")),
  {
    ssr: false,
  },
)

const PreviewBox = dynamic(
  async () => (await import("../engine")),
  {
    ssr: false,
  },
)

function App() {
  const [appState, setAppState] = useState<AppState>(AppState.INITIAL);
  const [generatedCode, setGeneratedCode] = useState<string>('');

  const [referenceImages, setReferenceImages] = useState<string[]>([]);
  const [referenceText, setReferenceText] = useState<string>('');

  const [executionConsole, setExecutionConsole] = useState<string[]>([]);
  const [updateInstruction, setUpdateInstruction] = useState("");
  const [partValue, setPartValue] = useState<{
    uid: string;
    message: string
  }>({ uid: '', message: '' });

  const {
    dataUrls,
    setDataUrls,
  } = useContext(UploadFileContext)

  // Settings
  const { settings, setSettings, initCreate, setInitCreate, initCreateText, setInitCreateText } = useContext(SettingContext);

  const { history, addHistory, currentVersion, setCurrentVersion, resetHistory, updateHistoryCode } = useContext(HistoryContext);
  const { enableEdit, setEnableEdit, device, setDevice } = useContext(EditorContext)
  const [tabValue, setTabValue] = useState<string>('desktop')
  const [openDialog, setOpenDialog] = useState<boolean>(false);

  // Tracks the currently shown version from app history

  const [shouldIncludeResultImage, setShouldIncludeResultImage] =
    useState<boolean>(false);

  const router = useRouter();
  const nextRouter = useNextRouter();

  const wsRef = useRef<AbortController>(null);
  const initFn = useDebounceFn(() => {
    if (dataUrls.length) {
      doCreate(dataUrls, '');
      setDataUrls([]);
    }
  }, {
    wait: 300
  });
  const initTextFn = useDebounceFn(() => {
    if (initCreateText) {
      doCreate([], initCreateText);
      setInitCreateText('');
    }
  }, {
    wait: 300
  });

  // When the user already has the settings in local storage, newly added keys
  // do not get added to the settings so if it's falsy, we populate it with the default
  // value
  useEffect(() => {
    if (!settings.generatedCodeConfig) {
      setSettings({
        ...settings,
        generatedCodeConfig: GeneratedCodeConfig.REACT_ANTD,
      });
    }

  }, [settings.generatedCodeConfig, setSettings]);

  useEffect(() => {
    const slug = nextRouter.query.slug;
    if (slug === 'create') {
      if (dataUrls.length) {
        initFn.run();
      }
      if (initCreateText) {
        initTextFn.run();
      }
    }
  }, [initCreate, dataUrls, initCreateText]);

  const takeScreenshot = async (): Promise<string> => {
    const iframeElement = document.querySelector(
      ".lc-simulator-content-frame"
    ) as HTMLIFrameElement;
    if (!iframeElement?.contentWindow?.document.body) {
      return "";
    }

    const canvas = await html2canvas(iframeElement.contentWindow.document.body);
    const png = canvas.toDataURL("image/png");
    return png;
  };

  const downloadCode = () => {
    // Create a blob from the generated code
    const blob = new Blob([generatedCode], { type: "text/html" });
    const url = URL.createObjectURL(blob);

    // Create an anchor element and set properties for download
    const a = document.createElement("a");
    a.href = url;
    a.download =  "index.html"; // Set the file name for download
    document.body.appendChild(a); // Append to the document
    a.click(); // Programmatically click the anchor to trigger download

    // Clean up by removing the anchor and revoking the Blob URL
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  };

  const reset = () => {
    setAppState(AppState.INITIAL);
    setGeneratedCode("");
    setReferenceImages([]);
    setExecutionConsole([]);
    resetHistory();
  };

  function doGenerateCode(
    params: CodeGenerationParams,
    parentVersion: number | null
  ) {
    setExecutionConsole([]);
    setAppState(AppState.CODING);

    // Merge settings with params
    const updatedParams = { ...params, ...settings, slug: nextRouter.query.slug };
    generateCode(
      wsRef,
      updatedParams,
      (token) => setGeneratedCode((prev) => prev + token),
      (code) => {
        setGeneratedCode(code);
        addHistory(params.generationType, updateInstruction, referenceImages, referenceText, code, partValue.message);
      },
      (line) => setExecutionConsole((prev) => [...prev, line]),
      () => {
        setAppState(AppState.CODE_READY);
      },
      (error) => {
        if (error === 'No openai key, set it') {
          setOpenDialog(true);
        }
      },
    );
  }

  // Initial version creation
  function doCreate(referenceImages: string[], text: string, slug?: string) {
    reset();

    setReferenceImages(referenceImages);
    setReferenceText(text);
    // console.log('doCreate begin', referenceImages[0])

    if (referenceImages.length > 0 || text || slug) {
      if(settings.useCVModel){
        detectComponents(referenceImages[0])
        .then((res) => res.json())
        .then((data) => {
          doGenerateCode(
            {
              generationType: "create",
              image: referenceImages[0],
              text,
              components: data?.componentPosition,
            },
            currentVersion
          );
        })
      }else{
        doGenerateCode(
          {
            generationType: "create",
            image: referenceImages[0],
            text,
          },
          currentVersion
        );
      }    
    }
  }

  // Subsequent updates
  async function doUpdate(partData?: any) {
    if (currentVersion === null) {
      toast.error(
        "No current version set. Contact support or open a Gitee issue."
      );
      return;
    }

    const updatedHistory = [
      ...extractHistoryTree(history, currentVersion),
      updateInstruction,
    ];

    if (shouldIncludeResultImage) {
      const resultImage = await takeScreenshot();
      doGenerateCode(
        {
          generationType: "update",
          image: referenceImages[0],
          text: referenceText,
          resultImage: resultImage,
          history: updatedHistory,
        },
        currentVersion
      );
    } else {
      doGenerateCode(
        {
          generationType: "update",
          image: referenceImages[0],
          text: referenceText,
          history: updatedHistory,
        },
        currentVersion
      );
    }

    setGeneratedCode("");
    setUpdateInstruction("");
  }

  async function doPartUpdate(partData?: any) {
    const { uid, message } = partData;
    const codeHtml = generatedCode;
    updateHistoryCode(codeHtml)
    const updatePrompt = `
Find the element with attribute data-uid="${uid}" and change it as described below:
${message}
Re-output the code and do not need to output the data-uid attribute.
    `;
    setPartValue(partData);
    setUpdateInstruction(updatePrompt);
  }


  useEffect(() => {
    const errorUpdate = updateInstruction.includes('Fix the code error and re-output the code');
    const partUpdate = updateInstruction.includes('Re-enter the code.');
    if (errorUpdate || partUpdate || updateInstruction) {
      doUpdate();
    }
  }, [
    updateInstruction
  ])

  async function fixBug(error: {
    message: string;
    stack: string;
  }) {
    const errorPrompt = `
Fix the code error and re-output the code.
error message:
${error.message}
${error.stack}
    `;
    setUpdateInstruction(errorPrompt);
  }

  const copyCode = useCallback(() => {
    copy(generatedCode);
    toast.success("Copied to clipboard");
  }, [generatedCode]);


  return (
    <div className="dark:bg-black dark:text-white h-full">
      <div className="fixed inset-y-0 z-40 flex w-[200px] flex-col">
        <div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-4 py-4 dark:bg-zinc-950 dark:text-white">
          {(appState === AppState.CODING ||
            appState === AppState.CODE_READY) && (
              <>
                {appState === AppState.CODING && (
                  <div>
                    <CodePreview code={generatedCode} />
                  </div>
                )}


                <div>
                  <div className="grid w-full gap-2">
                    <div className="flex justify-between items-center gap-x-2">
                      <div className="font-500 text-xs text-slate-700 dark:text-white">
                        Include screenshot of current version?
                      </div>
                      <Switch
                        checked={shouldIncludeResultImage}
                        onCheckedChange={setShouldIncludeResultImage}
                        className="dark:bg-gray-700"
                      />
                    </div>
                  </div>
                </div>

                {/* Reference image display */}
                <div className="flex flex-col mt-2">
                  <div className="flex flex-col">
                    <div>
                      {referenceText ? (
                        <div className="border p-1 border-slate-200 w-full rounded bg-[#ebebeb]">
                          <p className="text-sm">
                            {referenceText}
                          </p>
                        </div>
                      ) : (
                        (referenceImages[0]) ? (
                          <img
                            className="w-[340px] border border-gray-200 rounded-md"
                            src={referenceImages[0]}
                            alt="Reference"
                          />
                        ) : <></>
                      )}
                    </div>
                    <div className="text-gray-400 uppercase text-sm text-center mt-1">
                      Original Info
                    </div>
                  </div>
                  <div className="bg-gray-400 px-4 py-2 rounded text-sm hidden">
                    <h2 className="text-lg mb-4 border-b border-gray-800">
                      Console
                    </h2>
                    {executionConsole.map((line, index) => (
                      <div
                        key={index}
                        className="border-b border-gray-400 mb-2 text-gray-600 font-mono"
                      >
                        {line}
                      </div>
                    ))}
                  </div>
                </div>
              </>
            )}
          {
            <HistoryDisplay
              history={history}
              currentVersion={currentVersion}
              revertToVersion={(index) => {
                if (
                  index < 0 ||
                  index >= history.length ||
                  !history[index]
                )
                  return;
                setCurrentVersion(index);
                setGeneratedCode(history[index].code);
              }}
              shouldDisableReverts={appState === AppState.CODING}
            />
          }
        </div>
      </div>
      <main className="pl-[200px] relative h-full flex flex-col pb-10">
        <div className="w-[90%] ml-[5%] flex-1 mt-4">
          <div className="flex absolute gap-2">
            <Button
              onClick={() => {
                reset();
                router.push('/', { scroll: false })
              }}
            >
              <FaChevronLeft />
              Return</Button>
            {appState === AppState.CODE_READY && (
              <>
                <span
                  onClick={copyCode}
                  className="hover:bg-slate-200 rounded-sm w-[36px] h-[36px] flex items-center justify-center border-black border-2"
                >
                  <FaCopy />
                </span>
                <span
                  onClick={downloadCode}
                  className="hover:bg-slate-200 rounded-sm w-[36px] h-[36px] flex items-center justify-center border-black border-2"
                >
                  <FaDownload />
                </span>
              </>
            )}

            {appState === AppState.CODING && (
              <>
                <span className="flex items-center gap-x-1">
                  <Spinner />
                  {executionConsole.slice(-1)[0]}
                </span>
              </>
            )}
          </div>
          <Tabs onValueChange={(e: any) => {
            setTabValue(e)
          }} className="h-full flex flex-col" defaultValue={'desktop'}>
            <div className="flex justify-end mr-8 mb-4">
              <TabsList>
                {
                    <>
                      <TabsTrigger value="desktop" className="flex gap-x-2">
                        <FaDesktop /> Webpage
                      </TabsTrigger>
                    </>
                }
                <TabsTrigger value="code" className="flex gap-x-2">
                  <FaCode />
                  Code
                </TabsTrigger>
              </TabsList>
            </div>

            <div
              className={
                classNames('h-full flex justify-center', {
                  'hidden': tabValue !== 'desktop'
                })
              }
            >
              <div
                className={
                  classNames("h-full", {
                    "w-full": device === deviceType.PC,
                    "w-[375px]": device === deviceType.MOBILE
                  }
                  )}
              >
                <PreviewBox
                  code={generatedCode}
                  appState={appState}
                  sendMessageChange={(data) => {
                    doPartUpdate(data);
                  }}
                  generatedCodeConfig={settings.generatedCodeConfig}
                  history={history}
                  fixBug={fixBug}
                />
              </div>

            </div>
            <div
              className={
                classNames('h-full', {
                  'hidden': tabValue !== 'code'
                })
              }
            >
              <CodeTab
                code={generatedCode}
                setCode={setGeneratedCode}
                settings={settings}
              />
            </div>
          </Tabs>
        </div>
        <div className="flex justify-center mt-10">
          <div className="w-[520px] rounded-md shadow-sm ">
            <UpdateChatInput updateSendMessage={(message: string) => {
              setUpdateInstruction(message);
              setPartValue({
                uid: '',
                message: ''
              });
            }} />
          </div>
        </div>
      </main>
      <span className="hidden">
        <SettingsDialog
          settings={settings}
          setSettings={setSettings}
          openDialog={openDialog}
          setOpenDialog={setOpenDialog}
        />
      </span>
    </div>
  );
}

export default App;